#include <stdlib.h>
#include "lib.h"

#include "CDrawGame.h"

#include "CPokerConnection.h"
#include "CSavedGame.h"

#include "shue.h"
#include "cardmgr.h"


CardMgr CDrawGame::m_stdMgr;

const int CDrawGame::CDRAW_NUMDECKS=1;

int32 CDrawGame::m_anPayTable[MAX_CDRAW_PAY_FREQUENCIES][HR_ROYALFLUSH + 1] =
{
    {   1, 1, 1, 2, 3, 5, 7, 20, 50, 100   }     // pay table
};

int32 CDrawGame::m_anProgressivePayTable[MAX_CDRAW_PROGRESSIVE_PAY_FREQUENCIES][HR_ROYALFLUSH + 1] =
{
    { 0, 0, 0, 0, 0, 7500, 10000, 50000, 10, 100 }    // progressive pay (frequency) table
};



///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::CDrawGame(CPokerConnection* conn, int32 bet, int32 progressiveBet)
///
/// Constructor used when starting a brand new game
///
/// @param  pConn           (in) The object that provides the connection back to the client.
/// @param  nBet            (in) The amount of initial (ante) bet that the player made
/// @param  nProgressiveBet (in) The amount of the player's bet on the progressive.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
CDrawGame::CDrawGame(CPokerConnection* pConn, int32 nBet, int32 nProgressiveBet)
: m_drawnDealerCards(&m_stdMgr),
  m_drawnPlayerCards(&m_stdMgr)  
{
    m_pShue = NULL;
    m_nDeckSeed = 0;
    m_pConn = pConn;
    m_nBet = nBet;
    m_nProgressiveBet = nProgressiveBet;
 
    m_pOrigDealer = new CDrawHand(&m_stdMgr);
    m_pDealer    = new CDrawHand(&m_stdMgr);
    m_pPlayer    = new CDrawHand(&m_stdMgr);

    m_nPayTable     = pConn->getPayTable();
    m_nProgPayTable = pConn->getProgressivePayTable();
    

    m_bGameOK = true;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::CDrawGame(CPokerConnection* conn, CSavedGame* saved)
///
/// This constructor is used when restoring an existing unfinished hand of Draw
///
/// @param  pConn   (in) connects us back to the client
/// @param  pSaved  (in) the state information that we'll be restoring.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
CDrawGame::CDrawGame(CPokerConnection* pConn, CSavedGame* pSaved)
: m_drawnDealerCards(&m_stdMgr),
  m_drawnPlayerCards(&m_stdMgr)  
{    
    m_pConn = pConn;
    
    m_nBet = 0;

    m_pOrigDealer = new CDrawHand(&m_stdMgr);

    m_pDealer = m_pPlayer = NULL;

    // everything else is restored in restore()
    m_bGameOK = restore(pSaved);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::~CDrawGame()
///
/// Destructor
///
///////////////////////////////////////////////////////////////////////////////////////////////////
CDrawGame::~CDrawGame()
{
    delete m_pOrigDealer;
    m_pOrigDealer = NULL;

    delete m_pPlayer;
    m_pPlayer = NULL;

    delete m_pDealer;
    m_pDealer = NULL;
    
    delete m_pShue;
    m_pShue = NULL;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::restore(CSavedGame* pSaved)
///
/// Does the actual work of loading the saved state back into this active game object.
///
/// @param  pSaved  (in) the state information that we'll be restoring.
///
/// @return always returns true.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::restore(CSavedGame* pSaved)
{
    setMalfunction(pSaved->bMalfunction);   // kharmon.  01-05-06.  #4250.  Restore the malfunction state.

    m_nBet = pSaved->bet;

    m_nProgressiveBet = pSaved->progressiveBet;

    m_nPayTable       = pSaved->payTable;

    m_nProgPayTable   = m_pConn->getProgressivePayTable();

    m_pDealer    = new CDrawHand(&m_stdMgr);
    m_pPlayer    = new CDrawHand(&m_stdMgr);

    // Use the deck seed to regenerate the cards.
    ShueGameInfo sgi;
    sgi.gameId = CARIBBEAN_DRAW;
    sgi.machId = 0;
    
    m_pShue = new Shue(&m_stdMgr, CDRAW_NUMDECKS, sgi);

    ShueSave save;
    save.seed = pSaved->nDeckSeed;
    save.next = 0;
    m_pShue->restore(&save);

    m_pPlayer->add(m_pShue->getCard());
    m_pDealer->add(m_pShue->getCard());
    m_pPlayer->add(m_pShue->getCard());
    m_pDealer->add(m_pShue->getCard());
    m_pPlayer->add(m_pShue->getCard());
    m_pDealer->add(m_pShue->getCard());
    m_pPlayer->add(m_pShue->getCard());
    m_pDealer->add(m_pShue->getCard());
    m_pPlayer->add(m_pShue->getCard());
    m_pDealer->add(m_pShue->getCard());

    return !hasMalfunction();   // The game is not "OK" if we have a malfunction.
}

  

///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     bool8 CDrawGame::deal(int32 nBalance)
///
/// Called when the user wants us to deal the cards for a new hand of Draw.  This function 
/// creates the player and dealer hands records all of the
/// relevant information in the database, and sends part of the deal results back to the client.
///
/// @param  nBalance    (in)  the player's current balance
///
/// @return true if the deal completes successfully, false otherwise.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::deal(int32 nBalance)
{
    bool  bResult = false;

	ShueGameInfo sgi;
	sgi.gameId = CARIBBEAN_DRAW;
	sgi.machId = 0;

    #ifdef FORCEDSHOE 
       sgi.pid=m_pConn->GetPID();
    #endif


    m_pShue = new Shue(&m_stdMgr, CDRAW_NUMDECKS, sgi);

    if (m_pShue && m_pDealer && m_pPlayer)
    {
        // pull the cards out of the shue and assign them in the correct order.
        m_pShue->shuffle();

        ShueSave save;
        m_pShue->save(&save);
        m_nDeckSeed = save.seed;

        m_pPlayer->add(m_pShue->getCard());
        m_pDealer->add(m_pShue->getCard());
        m_pPlayer->add(m_pShue->getCard());
        m_pDealer->add(m_pShue->getCard());
        m_pPlayer->add(m_pShue->getCard());
        m_pDealer->add(m_pShue->getCard());
        m_pPlayer->add(m_pShue->getCard());
        m_pDealer->add(m_pShue->getCard());
        m_pPlayer->add(m_pShue->getCard());
        m_pDealer->add(m_pShue->getCard());

        // see what rank the player has
        m_pPlayer->rankHand();
        int32 nProgressiveRank = m_pPlayer->getRank();

        // if the player made the progressive bet, and has won, the player doesn't get to draw any new cards.
        bool8 bDisableDraw = (m_nProgressiveBet>0 && m_anProgressivePayTable[m_nProgPayTable][nProgressiveRank]>0);

        // alert the database as to what has happened and let it run it's checks
        if (m_pConn->getDBInterface()->trackDeal(this))
        {
            // Send the part of the deal that the user needs to see at this stage of the game.
            m_pConn->msgStart();
            m_pConn->msgAdd((uint8)'D');
            m_pConn->msgReleaseBuf(m_pDealer->outlast(m_pConn->msgGetBuf()));
            m_pConn->msgAdd((uint8)(m_pPlayer->getCount() & 0xff));
            m_pConn->msgReleaseBuf(m_pPlayer->out(m_pConn->msgGetBuf()));
            m_pConn->msgAdd(nBalance);
            m_pConn->msgAdd((uint8) bDisableDraw);
            m_pConn->msgEnd();

            bResult = true;
        }
    }

    return bResult;

}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     bool8 CDrawGame::raise(int32 nBalance)
///
/// Called when the user raises (aka calls).  This method figures out the outcome and payout 
/// info, updates the database, and then sends the final results back to the client.
///
/// @param  nBalance    The player's current balance
///
/// @return bool8   true on success, false otherwise
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::raise(int32 nBalance, uint8 *pucMsgBuf)
{
    return finish(true, nBalance, pucMsgBuf);
}



///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::fold(int32 nBalance)
///
/// Called when the user folds.  This methods figures out the outcome and payout info, updates
/// the database, and then sends the final results back to the client.
///
/// @param  nBalance    The player's current balance
///
/// @return bool8   true on success, false otherwise
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::fold(int32 nBalance)
{
    // the buck stops there.
    return finish(false, nBalance, NULL);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::send()
///
/// This method is called to tell us to send the "restored" game state to the client when picking 
/// up on an existing game that was started but not completed in a previous session.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void CDrawGame::send()
{
    m_pConn->msgAdd(m_nBet);
    m_pConn->msgAdd(m_nProgressiveBet);
    m_pConn->msgAdd(m_nPayTable);
    m_pConn->msgReleaseBuf(m_pDealer->outlast(m_pConn->msgGetBuf()));
    m_pConn->msgAdd((uint8)(m_pPlayer->getCount() & 0xff));
    m_pConn->msgReleaseBuf(m_pPlayer->out(m_pConn->msgGetBuf()));

    // see what rank the player has
    m_pPlayer->rankHand();
    int32 nProgressiveRank = m_pPlayer->getRank();

    // if the player made the progressive bet, and has won, the player doesn't get to draw any new cards.
    bool8 bDisableDraw = (m_nProgressiveBet>0 && m_anProgressivePayTable[m_nProgPayTable][nProgressiveRank]>0);

    m_pConn->msgAdd((uint8) bDisableDraw);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::save(CSavedGame*)
///
/// Theoretically called to tell us to load this game's state into the CSavedGame object.
/// In practice, does nothing.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void CDrawGame::save(CSavedGame*)
{

}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::finish(bool8 bRaised, int32 nBalance)
///
/// This method does all of the real work when the user folds or calls.  This method figures 
/// out the outcome and payout info, updates the database, and then sends the final results 
/// back to the client.
///
/// @param  bRaised     true if the player raised (called), false if the user folded
/// @param  nBalance    the player's current balance
///
/// @return bool8       true on success, false otherwise
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::finish(bool8 bRaised, int32 nBalance, uint8 *pucMsgBuf)
{
    int32 anPayouts[2];         // 0th element is ante payout, 1st element is the call payout
    int32 nBalAdjust;           // the total amount to adjust the user's balance by
    int32 nProgPayout;          // how much of the change comes from progressive payout
    int32 nMultiBankPayout = 0; // 
    int32 nOutcome;             // the final outcome: win, lose, push, etc.
    bool8 bUseHighCard;         // whether we had to go to high cards to decide who won.

    bool bRetVal = false;

    // okay, call calcPayouts to figure out what the final results are
    if (!calcPayouts(anPayouts, &nProgPayout, &nMultiBankPayout, bRaised,
                     &bUseHighCard, &nOutcome, &nBalAdjust, pucMsgBuf))
    {
        // hmmmm...something went belly up...not good.
        return bRetVal;
    }

    // run the results that calcPayout came up with by the database....
    if (m_pConn->getDBInterface()->trackGameDone(bRaised, anPayouts[0], anPayouts[1], nProgPayout, 
                                                 nMultiBankPayout, nOutcome, nBalAdjust, &nBalance,
                                                 hasMalfunction(), this))                           // kharmon.  10-12-06.  #8226.  Now that the game is complete, it's safe to save the actual cards.
    {
        // the database has been alerted and didn't reject the results, so let the client know 
        // what's going on.
        m_pConn->msgStart();
        m_pConn->msgAdd((uint8)'F');

        // original dealer cards
        m_pConn->msgAdd((uint8)(m_pOrigDealer->getCount() & 0xff));
        m_pConn->msgReleaseBuf(m_pOrigDealer->out(m_pConn->msgGetBuf()));

        // final dealer cards
        m_pConn->msgAdd((uint8)(m_pDealer->getCount() & 0xff));
        m_pConn->msgReleaseBuf(m_pDealer->out(m_pConn->msgGetBuf()));

        // player cards
        m_pConn->msgAdd((uint8)(m_drawnPlayerCards.getCount() & 0xff));
        m_pConn->msgReleaseBuf(m_drawnPlayerCards.out(m_pConn->msgGetBuf()));

        // payout amounts
        m_pConn->msgAdd(anPayouts[0]);
        m_pConn->msgAdd(anPayouts[1]);
        m_pConn->msgAdd(nProgPayout);

        // the dealer and player ranks and high cards
        m_pConn->msgReleaseBuf(m_pOrigDealer->outrank(m_pConn->msgGetBuf()));
        m_pConn->msgReleaseBuf(m_pOrigDealer->outhigh(m_pConn->msgGetBuf()));
        m_pConn->msgReleaseBuf(m_pDealer->outrank(m_pConn->msgGetBuf()));
        m_pConn->msgReleaseBuf(m_pDealer->outhigh(m_pConn->msgGetBuf()));
        m_pConn->msgReleaseBuf(m_pPlayer->outrank(m_pConn->msgGetBuf()));
        m_pConn->msgReleaseBuf(m_pPlayer->outhigh(m_pConn->msgGetBuf()));

        // the OVERALL outcome for the game...
        m_pConn->msgAdd((uint8)(nOutcome & 0xff));
        m_pConn->msgAdd((uint8)(bUseHighCard & 0xff));

        // player's new balance
        m_pConn->msgAdd(nBalance);

        // paytable/progressive info.
        m_pConn->msgAdd((uint8)m_pConn->getPayTable());
        m_pConn->msgAdd((uint8)m_pConn->isProgressiveOn());
        m_pConn->msgAdd((uint8)m_pConn->getProgressivePayTable());
        m_pConn->msgEnd();

        // kharmon.  01-05-06.  #4250.  If the player hit a big progressive jackpot, 
        // this error will shut them down until we can verify things.
        if(hasMalfunction())
        {
            m_pConn->msgStart();
            m_pConn->msgAdd((uint8) 'E');
            m_pConn->msgAdd((uint8) 13);
            m_pConn->msgEnd();
        }

        // alert the connection object that this game is over and we can be deleted.
        m_pConn->gameDone();

        bRetVal = true;    
    }

    return bRetVal;

}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     CDrawGame::calcPayouts(int32* pnPayouts, int32* pnProgressivePayout, int32* pnMultiBankPayout, 
///                                  bool8 bRaised, bool8* pbUseHighCard, int32* pnOutcome, int32* pnBalAdjust)
///
/// Does the work of figuring out who won the hand and how that affects the money.
///
/// @param  pnPayouts           (out) array of 2 elements...0th element is ante payout, 1st element is raise/call payout.
/// @param  pnProgressivePayout (out) how much of the total payout comes from winning a progressive
/// @param  pnMultiBankPayout   (out) 
/// @param  bRaised             (in)  did the user raise/call or fold?
/// @param  pbUseHighCard       (out) will be true if someone won, false if there was a tie.
/// @param  pnOutcome           (out) Exactly who won, if anyone, and why they won.
/// @param  pnBalAdjust         (out) how much the user's balance needs to change.
///
/// @return bool8   true if nothing goes wrong, false otherwise.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 CDrawGame::calcPayouts(int32* pnPayouts, int32* pnProgressivePayout, int32* pnMultiBankPayout, bool8 bRaised, 
                             bool8* pbUseHighCard, int32* pnOutcome, int32* pnBalAdjust, uint8 *pucMsgBuf)
{
    int32 nWinnings=0;

    for(int32 nNdx=0;nNdx<5;++nNdx)
    {
        m_pOrigDealer->add(m_pDealer->getCardAt(nNdx));
    }

    m_pOrigDealer->rankHand();
    m_pOrigDealer->calcNthHighest(0);

    // Return dealer & player hand ranks & high card regardless of whether
    // player folded or not.  Although, not necessary when player folds, it's
    // nice to see what the dealer had (and how it would've ranked).
    if (!m_pDealer->rankHand())
    {
        return false;
    }

    m_pDealer->calcNthHighest(0);

    // see what rank the player has...
    if (!m_pPlayer->rankHand())
    {
        return false;
    }

    int32 nProgressiveRank = m_pPlayer->getRank();

    if(bRaised && pucMsgBuf[1]>0 && (m_nProgressiveBet==0 || m_anProgressivePayTable[m_nProgPayTable][nProgressiveRank]==0))
    {
        // The user wants to draw, and didn't win a progressive bet, so let them.
        int32 nDrawCount = pucMsgBuf[1];
        for(int32 nDrawNdx=0;nDrawNdx<nDrawCount;++nDrawNdx)
        {
            uint8 ucCard = 0;
            m_stdMgr.decode(&pucMsgBuf[2 + nDrawNdx*2], &ucCard);

            for(int32 nI=0;nI<5;++nI)
            {
                if(m_pPlayer->getCardAt(nI)==ucCard)
                {
                    uint8 ucNewCard = m_pShue->getCard();
                    m_drawnPlayerCards.add(ucNewCard);
                    m_pPlayer->getCards()[nI] = ucNewCard;
                    break;
                }
            }
        }

        // recalculate the rank using the new cards.
        if (!m_pPlayer->rankHand())
        {
            return false;
        }
    }
    
    m_pPlayer->calcNthHighest(0);

    if(bRaised)
    {
        // do any draws that need to be done for the dealer hand.
        m_pDealer->drawDealerCards(m_pShue, &m_drawnDealerCards);    
    }

    // kharmon.  11-22-05.  #7534, 7550.  Don't show tie card unless unless the hand ranks are the same.
    *pbUseHighCard = false;

    if (m_pDealer->getRank() > m_pPlayer->getRank())
    {
        *pnOutcome = O_DEALER_WON;
    }
    else if (m_pDealer->getRank() < m_pPlayer->getRank())
    {
        *pnOutcome = O_PLAYER_WON;
    }
    else
    {
        // kharmon.  11-22-05.  #7534, 7550.  Hmmmm...same rank, it may come down to a kicker.
        *pbUseHighCard = true;

        *pnOutcome = O_PUSH;

        // hmmmm...the dealer and the player have the same type of hand, look at the
        // ranks of the cards in the hand to who has the higher high card.
        for (int nI = 0; nI < m_pDealer->getCount(); nI++)
        {
            if (nI > 0)                     //0th high card already calculated above
            {
                m_pDealer->calcNthHighest(nI);
                m_pPlayer->calcNthHighest(nI);
            }

            if (m_pDealer->getHighCard() > m_pPlayer->getHighCard())
            {
                *pnOutcome = O_DEALER_WON;
                break;
            }
            else if (m_pDealer->getHighCard() < m_pPlayer->getHighCard())
            {
                *pnOutcome = O_PLAYER_WON;
                break;
            }
        }
    }

    ASSERT(m_nProgPayTable >= 0 && m_nProgPayTable < MAX_CDRAW_PROGRESSIVE_PAY_FREQUENCIES &&
           m_pPlayer->getRank() >= 0 && m_pPlayer->getRank() <= HR_ROYALFLUSH);


    // Progressive, anyone?
    if (m_nProgressiveBet > 0 && m_anProgressivePayTable[m_nProgPayTable][nProgressiveRank] > 0)
    {
        // the user made a progressive bet and is in the money.
        bool8 bSuccess;
        int32 nPotWinner;

        // how much should the hand get any money from the progressive pool?
        nPotWinner = m_anProgressivePayTable[m_nProgPayTable][nProgressiveRank];

        bool bPercentage = false;
        if (nPotWinner <= 100)  // everything above 100 is an absolute value, below is a percentage.
        {
            bPercentage = true;            
        }
        else
        {
            // kharmon.  02-03-06.  #9218.  Adjust for different currency multipliers.
            nPotWinner *= m_pConn->getProgressiveFixedAmountMultiplier();
        }
        
        // see what the mothership has to say about it...
        bSuccess = m_pConn->setProgressiveWinner(nPotWinner, pnProgressivePayout, pnMultiBankPayout, bPercentage);

        (*pnMultiBankPayout) *= 100;    // kharmon.  11-15-07.  #20294.  Need to do this for all wins, because we pass them into the db as millimoney, not money.

        // kharmon.  10-10-06.  #14842.  Need to do this for both 10% and 100% wins.
        if (nPotWinner == 10 || nPotWinner == 100)
        {            
            m_pConn->SendWinMail("Caribbean Draw", nPotWinner, *pnProgressivePayout);
        }

        if (!bSuccess)
        {
            // hmmmmm...something went seriously awry...abandon ship.
            return false;
        }
    }
    else
    {
        // the user either did not make a progressive bet, or didn't win anything.
        *pnProgressivePayout = 0;
    }

    
    // okay, now to add up all the money
    switch (*pnOutcome)
    {
        case O_DEALER_WON:            // dealer wins: -ante, -raise
            pnPayouts[0] = -m_nBet;
            pnPayouts[1] = -(m_nBet << 1);
            break;

        case O_PLAYER_WON:            // player wins: ante 1:1, raise payouttable:1
            pnPayouts[0] = m_nBet;
            pnPayouts[1] = (m_nBet << 1) * m_anPayTable[m_nPayTable][m_pPlayer->getRank()];
            break;

        case O_PUSH:
            *pbUseHighCard = false;
            pnPayouts[0] = 0;
            pnPayouts[1] = 0;
            break;
    }

    if (bRaised)
    {
        // player raised.
        if (!m_pDealer->qualifies())    // the dealer must have at least a pair of 4's
        {
            // dealer doesn't qualify: ante 1:1, raise pushed
            //
            pnPayouts[0] = m_nBet;
            pnPayouts[1] = 0;

            *pnOutcome = O_DEALER_NOQUALIFY;
        }

        nWinnings = m_nBet + pnPayouts[0] + (m_nBet << 1) + pnPayouts[1];
    }
    else
    {
        // Player folded:  lose original bet
        pnPayouts[0] = -m_nBet;
        pnPayouts[1] = 0;
        nWinnings = 0;

        *pnOutcome = O_PLAYER_FOLD;
    }

    *pnBalAdjust = nWinnings;

	// [AO 2008-04-07] Do not set a malfunction on big jackpot wins, just pay.
	*pnBalAdjust += *pnProgressivePayout;

    return true;
}


